package model

import (
	"fmt"
	"github.com/go-redis/redis/v8"
	"go.mongodb.org/mongo-driver/bson"
	"gogame/lib/database/mongo"
	myredis "gogame/lib/database/redis"
	"gogame/logger"
	"reflect"
	"strings"
	"time"
)

/*
------------------ model interface ------------------
*/

type ModelBaseInterface interface {
	OnlyKey(key string) string                                  // 获取缓存唯一key
	Set(key string, setData map[string]any)                     // 设置数据
	Remove(key string)                                          // 删除数据
	InsertOne(data any)                                         // 插入数据
	InsertMany(datas []any)                                     // 插入多条数据
	Get(key string, source any, isRedis ...bool) error          // 获取单条数据
	GetList(key string, sliceSource any, isRedis ...bool) error // 获取多条数据
}

type GetIdInterface interface {
	GetId() string
}

type GetUidInterface interface {
	GetUid() string
}

type ModelInterface interface {
	GetUidInterface
	GetIdInterface
}

// ChildInterFace 子类实现的DataChange 用于数据改变时通知子类作出对应的逻辑处理
type ChildInterFace interface {
	DataChange(uid, act string, datas ...any)
}

/*
------------------ model interface ------------------ end
*/

type ModelInfo struct {
	*ModelBase
	Mdb   *mongo.MongoStruct
	Redis *myredis.RedisStruct

	Child ChildInterFace
}

func NewModelInfo(dbName, uniKey string, options ...OptionFunc) *ModelInfo {
	_modelInfo := &ModelInfo{
		ModelBase: &ModelBase{
			DbName:     dbName,
			UniKey:     uniKey,
			WriteRedis: true,
		},
	}
	// 默认2小时过期
	SetExpireTime(time.Hour * 2)(_modelInfo)
	// 跨服
	if strings.Contains(dbName, "cross") {
		_modelInfo.Ver = "cross"
		_modelInfo.Mdb = CrossMdb
		_modelInfo.Redis = CrossRedis
	} else {
		// 本服
		_modelInfo.Mdb = Mdb
		_modelInfo.Redis = Redis
	}
	_modelInfo.ModelBase.Redis = _modelInfo.Redis

	for _, option := range options {
		option(_modelInfo)
	}

	return _modelInfo
}

// dataChange 数据改变
func (m *ModelInfo) dataChange(uid, act string, datas ...any) {
	_modelLog := &ModelLog{
		Id:    C.GetObjectId(),
		Uid:   uid,
		Act:   act,
		Table: m.DbName,
	}
	switch act {
	case "Set":
		_modelLog.D = append(_modelLog.D, bson.M{m.UniKey: datas[0]}, datas[1])
	case "Remove":
		_modelLog.D = append(_modelLog.D, bson.M{m.UniKey: datas[0]})
	case "InsertOne", "InsertMany":
		_modelLog.D = append(_modelLog.D, datas...)
	}
	SaveModelLog(m.Ver, _modelLog)

	// 通知子类数据改变
	if m.Child != nil {
		m.Child.DataChange(uid, act, datas...)
	}
}

// writeRedis 数据写入redis
func (m *ModelInfo) _writeRedis(key string, setData any, isPipeline ...bool) {
	if len(isPipeline) > 0 && isPipeline[0] == true {
		m.Redis.Pipeline.HMSet(key, setData)
	} else {
		m.Redis.HMSet(key, setData)
	}
}

// WriteRedis 数据写入redis 格式化key
func (m *ModelInfo) writeRedis(key string, setData any, isPipeline ...bool) {
	if !m.WriteRedis {
		return
	}
	_key := m.OnlyKey(key)
	//logger.Print("Set Table:%s Set Key: %s  setData: %v", m.DbName, key, setData)
	m._writeRedis(_key, setData, isPipeline...)
}

// Set 设置数据
func (m *ModelInfo) Set(uid, key string, setData map[string]any) {
	setData["lt"] = C.Now()
	m.writeRedis(key, setData)
	m.dataChange(uid, "Set", key, setData)

}

// Remove 删除数据
func (m *ModelInfo) Remove(uid, key string) {
	var redisKey string
	switch m.UniKey {
	case "uid":
		redisKey = uid
	case "_id":
		redisKey = key

		if m.WriteRedis {
			// 移除list
			m.Redis.HDel(m.OnlyKey(uid), redisKey)
		}

	}

	if m.WriteRedis {
		m.Redis.Del(m.OnlyKey(key))
	}
	m.dataChange(uid, "Remove", key)
}

// insert 插入数据
func (m *ModelInfo) insertOne(uid string, data any, isPipeline bool) {
	if !m.WriteRedis {
		return
	}

	_modelInterface, ok := data.(ModelInterface)
	// 插入的数据不是pb.Model
	if !ok {
		logger.WorkerLog.Warn(fmt.Sprintf("model.base insert data is not pb.Model!!!\ndata--->%+v", data))
		return
	}

	var key string
	switch m.UniKey {
	case "uid":
		key = uid
	case "_id":
		key = _modelInterface.GetId()

		// 加入list
		_setMap := map[string]any{
			key: m.OnlyKey(key),
		}
		m.writeRedis(uid, _setMap, isPipeline)
	}

	m.writeRedis(key, data, isPipeline)
}

// InsertOne 插入单条数据
func (m *ModelInfo) InsertOne(uid string, data any) {
	if m.UniKey == "uid" {
		m.AddModelBase(uid)
	}
	m.insertOne(uid, data, false)
	m.dataChange(uid, "InsertOne", data)
}

// InsertMany 插入多条数据 管道批量写入
func (m *ModelInfo) InsertMany(uid string, datas []any) {
	m.AddModelBase(uid)
	if len(datas) == 0 {
		logger.WorkerLog.Warn("model.base Inserts err data is []")
		return
	}

	for _, data := range datas {
		m.insertOne(uid, data, true)
	}
	_, err := m.Redis.Pipeline.Exec()
	if err != nil {
		logger.WorkerLog.Warn(fmt.Sprintf("model.base Inserts setRedis fial\nerr-->%v", err))
	}

	m.dataChange(uid, "InsertMany", datas...)
}

// Get 获取单条数据
func (m *ModelInfo) Get(key string, source any, isRedis ...bool) error {
	if m.UniKey == "uid" {
		m.UpdateLastTime(key)
	}

	isMongo := false
	if len(isRedis) > 0 && isRedis[0] == false {
		isMongo = true
	}

	_key := m.OnlyKey(key)
	//fmt.Println("Get Key-->", _key)
	// 默认从redis取
	if !isMongo {
		err := m.Redis.HGetAll(_key, source)
		_toMongo := false
		// redis err or 为空
		if err != nil {
			_toMongo = true
		}

		// redis取不到从mongodb取
		if _toMongo {
			err = m.Get(key, source, false)
			// 设置到redis
			if err == nil {
				// uniKey为uid才处理 为_id在GetList处理
				if m.UniKey == "uid" {
					m.AddModelBase(key)
				}

				m.writeRedis(key, source)
			}
		}

		return nil

	} else {
		// 从mongo取
		return m.Mdb.FindOneByReflect(m.DbName, bson.M{m.UniKey: key}, source)

	}

}

// GetList 获取多条数据
func (m *ModelInfo) GetList(uid string, sliceSource any, isRedis ...bool) error {
	m.UpdateLastTime(uid)
	isMongo := false
	if len(isRedis) > 0 && isRedis[0] == false {
		isMongo = true
	}

	_key := m.OnlyKey(uid)
	// 默认从redis取
	if !isMongo {
		uniKey2RedisDataKey, err := m.Redis.Client.HGetAll(m.Redis.Context(), _key).Result()
		_toMongo := false
		// redis err or 为空
		if err != nil || len(uniKey2RedisDataKey) == 0 {
			_toMongo = true
		}

		//if gameconfig.ServerConfig.GetGameVer() == "debug" {
		//	_toMongo = true
		//}

		// redis取不到从mongodb取
		if _toMongo {
			_anyList, err := m.Mdb.FindByReflectToList(m.DbName, bson.M{"uid": uid}, sliceSource)
			// 设置到redis
			if err == nil && len(_anyList) > 0 {
				_setData := map[string]any{}
				// 设置每条数据
				for _, h := range _anyList {
					id := h.(GetIdInterface).GetId()
					uniKey, redisDataKey := id, m.OnlyKey(id)
					_setData[uniKey] = redisDataKey
					m.Redis.Pipeline.HMSet(redisDataKey, h)
				}
				if len(_setData) > 0 {
					// 设置所有数据的key集合
					m.Redis.Pipeline.HMSet(_key, _setData)
				}
				_, err = m.Redis.Pipeline.Exec()
				if err != nil {
					logger.WorkerLog.Warn(fmt.Sprintf("model.base GetList setRedis fial\nerr-->%v", err))
					return err
				}

				m.AddModelBase(uid)

			}
		} else {
			_pipeline := myredis.NewPipeline(m.Redis.Client, m.Redis.Ver)
			// pipeline查出所有数据
			for _, redisDataKey := range uniKey2RedisDataKey {
				_pipeline.HGetAll(redisDataKey)
			}
			_execRes, err := _pipeline.Exec()
			if err != nil {
				logger.WorkerLog.Warn(fmt.Sprintf("model.base GetList find redis data fial\nerr-->%v", err))
			}

			destSlice := reflect.Indirect(reflect.ValueOf(sliceSource)) // 实际值
			destType := destSlice.Type().Elem().Elem()                  // 值类型
			// 所有数据遍历出来
			for _, v := range _execRes {
				_toVal := reflect.New(destType)
				v.(*redis.StringStringMapCmd).Scan(_toVal.Elem().Addr().Interface())
				destSlice.Set(reflect.Append(destSlice, _toVal))
			}
		}

		return nil

	} else {
		// 从mongo取
		m.Mdb.FindByReflect("hero", bson.M{"uid": uid}, sliceSource)
		return nil

	}

}
